import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
from scipy import signal
from scipy.fft import fft, fftfreq
import warnings
warnings.filterwarnings('ignore')

def analyze_hit_periodicity():
    """Analyze periodicity of hit patterns"""
    
    # Read detailed data
    try:
        df_details = pd.read_csv('cache_access_details.csv')
        print(f"Detailed data shape: {df_details.shape}")
    except FileNotFoundError:
        print("cache_access_details.csv file not found")
        return
    
    # Less aggressive sampling - keep more data for periodicity analysis
    df_details = df_details.groupby(['experiment_name','matrix_type'], group_keys=False).apply(
        lambda x: x.iloc[:min(10000, len(x))]  # Keep up to 10,000 points per group
    ).reset_index(drop=True)
    print(f"\nSampled data shape: {df_details.shape}")
    
    # Data preprocessing
    df_details = df_details.sort_values(['experiment_name', 'matrix_type', 'timestamp'])
    df_details['sequence_id'] = df_details.groupby(['experiment_name', 'matrix_type']).cumcount()
    
    # Convert hit to numeric (True=1, False=0)
    df_details['hit_numeric'] = df_details['hit'].astype(int)
    
    # Get all experiments
    experiments = df_details['experiment_name'].unique()
    print(f"Found {len(experiments)} experiment(s): {experiments}")
    
    for exp in experiments:
        print(f"\nAnalyzing experiment: {exp}")
        analyze_single_experiment(df_details, exp)
    
    # Only do cross-experiment analysis if we have multiple experiments
    if len(experiments) > 1:
        analyze_cross_experiment_periodicity(df_details)
    else:
        print("\nOnly one experiment found, skipping cross-experiment analysis")

def analyze_single_experiment(df_details, experiment_name):
    """Analyze hit periodicity for a single experiment"""
    
    exp_data = df_details[df_details['experiment_name'] == experiment_name]
    
    # Create plots
    fig, axes = plt.subplots(2, 3, figsize=(18, 10))
    fig.suptitle(f'Hit Periodicity Analysis - {experiment_name}', fontsize=16, fontweight='bold')
    
    matrix_types = ['A', 'B', 'C']
    colors = {'A': 'blue', 'B': 'red', 'C': 'green'}
    
    for idx, matrix_type in enumerate(matrix_types):
        matrix_data = exp_data[exp_data['matrix_type'] == matrix_type]
        
        print(f"Matrix {matrix_type} data points: {len(matrix_data)}")
        
        if len(matrix_data) < 10:  # Too little data to analyze
            print(f"  Skipping matrix {matrix_type} - insufficient data")
            continue
            
        # Extract hit sequence
        hit_sequence = matrix_data['hit_numeric'].values
        sequence_ids = matrix_data['sequence_id'].values
        
        # Subplot 1: Original hit/miss sequence
        ax1 = axes[0, idx]
        display_points = min(1000, len(hit_sequence))
        ax1.step(sequence_ids[:display_points], hit_sequence[:display_points], where='post', 
                color=colors[matrix_type], alpha=0.7, linewidth=1)
        ax1.set_xlabel('Access Sequence Number')
        ax1.set_ylabel('Hit(1)/Miss(0)')
        ax1.set_title(f'Matrix {matrix_type} - Hit/Miss Sequence')
        ax1.grid(True, alpha=0.3)
        ax1.set_ylim(-0.1, 1.1)
        
        # Subplot 2: Moving window hit rate
        ax2 = axes[1, idx]
        window_size = min(100, len(hit_sequence) // 10)  # Adaptive window size
        hit_rate_moving = pd.Series(hit_sequence).rolling(
            window=window_size, min_periods=1).mean()
        
        display_points = min(1000, len(hit_rate_moving))
        ax2.plot(sequence_ids[:display_points], hit_rate_moving.values[:display_points], 
                color=colors[matrix_type], alpha=0.7, linewidth=2)
        ax2.set_xlabel('Access Sequence Number')
        ax2.set_ylabel(f'Moving Window Hit Rate (window={window_size})')
        ax2.set_title(f'Matrix {matrix_type} - Moving Window Hit Rate')
        ax2.grid(True, alpha=0.3)
        ax2.set_ylim(0, 1)
        
        # Periodicity analysis - this should now print results
        analyze_periodicity_methods(hit_sequence, matrix_type, experiment_name)
    
    plt.tight_layout()
    plt.savefig(f'hit_periodicity_{experiment_name}.png', dpi=300, bbox_inches='tight')
    plt.show()

def analyze_periodicity_methods(hit_sequence, matrix_type, experiment_name):
    """Analyze periodicity using multiple methods"""
    
    print(f"\n--- Matrix {matrix_type} Periodicity Analysis ---")
    print(f"Sequence length: {len(hit_sequence)}")
    print(f"Overall hit rate: {np.mean(hit_sequence):.3f}")
    
    if len(hit_sequence) < 50:  # Sequence too short for meaningful analysis
        print("Sequence too short, skipping periodicity analysis")
        return
    
    # Method 1: Autocorrelation analysis
    try:
        autocorr = signal.correlate(hit_sequence - np.mean(hit_sequence), 
                                   hit_sequence - np.mean(hit_sequence), 
                                   mode='full')
        autocorr = autocorr[len(autocorr)//2:]
        autocorr = autocorr / autocorr[0]  # Normalize
        
        # Find peaks (indicators of periodicity)
        peaks, properties = signal.find_peaks(autocorr[:100], height=0.3, distance=5)
        
        if len(peaks) > 1:  # First peak at lag=0, we care about subsequent peaks
            # Remove the first peak (lag=0) if present
            if peaks[0] == 0:
                peaks = peaks[1:]
                if len(properties['peak_heights']) > 1:
                    peak_heights = properties['peak_heights'][1:]
                else:
                    peak_heights = []
            else:
                peak_heights = properties['peak_heights']
            
            if len(peaks) > 0:
                dominant_period = peaks[np.argmax(peak_heights)] if len(peak_heights) > 0 else peaks[0]
                max_strength = np.max(peak_heights) if len(peak_heights) > 0 else 0.5
                print(f"Autocorrelation analysis - Dominant period: {dominant_period} accesses")
                print(f"Autocorrelation strength: {max_strength:.3f}")
            else:
                print("Autocorrelation analysis: No clear periodicity detected")
        else:
            print("Autocorrelation analysis: No clear periodicity detected")
            
    except Exception as e:
        print(f"Autocorrelation analysis failed: {e}")
    
    # Method 2: Fourier transform analysis
    try:
        # Apply Fourier transform to the sequence
        yf = fft(hit_sequence - np.mean(hit_sequence))
        xf = fftfreq(len(hit_sequence))
        
        # Take only positive frequencies
        idx = np.where(xf > 0)
        xf_pos = xf[idx]
        yf_pos = 2.0/len(hit_sequence) * np.abs(yf[idx])
        
        # Find main frequency (skip DC component at index 0)
        if len(yf_pos) > 1:
            main_freq_idx = np.argmax(yf_pos[1:]) + 1
            main_freq = xf_pos[main_freq_idx]
            main_amplitude = yf_pos[main_freq_idx]
            
            if main_freq > 0 and main_amplitude > 0.1:  # Threshold for significance
                period = 1 / main_freq
                print(f"Fourier analysis - Dominant period: {period:.1f} accesses (frequency: {main_freq:.4f})")
                print(f"Fourier amplitude: {main_amplitude:.3f}")
            else:
                print("Fourier analysis: No significant periodic frequency detected")
        else:
            print("Fourier analysis: Insufficient data for frequency analysis")
            
    except Exception as e:
        print(f"Fourier analysis failed: {e}")
    
    # Method 3: Statistical pattern analysis
    analyze_hit_patterns(hit_sequence, matrix_type)

def analyze_hit_patterns(hit_sequence, matrix_type):
    """Analyze hit/miss patterns"""
    
    # Convert sequence to string for pattern analysis
    hit_str = ''.join(str(int(x)) for x in hit_sequence[:1000])  # Analyze first 1000 points
    
    # Look for common patterns
    patterns = {
        'Consecutive hits': '11',
        'Consecutive misses': '00', 
        'Alternating pattern': '1010',
        'Long hit streak': '1'*5,
        'Long miss streak': '0'*5
    }
    
    print("Common pattern statistics:")
    for pattern_name, pattern in patterns.items():
        count = hit_str.count(pattern)
        if count > 0:
            print(f"  {pattern_name} ('{pattern}'): appears {count} times")
    
    # Calculate average consecutive hit/miss lengths
    current_val = hit_sequence[0]
    current_streak = 1
    streaks = []
    
    for i in range(1, len(hit_sequence)):
        if hit_sequence[i] == current_val:
            current_streak += 1
        else:
            streaks.append((current_val, current_streak))
            current_val = hit_sequence[i]
            current_streak = 1
    streaks.append((current_val, current_streak))
    
    hit_streaks = [length for val, length in streaks if val == 1]
    miss_streaks = [length for val, length in streaks if val == 0]
    
    if hit_streaks:
        avg_hit_streak = np.mean(hit_streaks)
        max_hit_streak = max(hit_streaks)
        print(f"Average consecutive hit length: {avg_hit_streak:.2f}")
        print(f"Maximum consecutive hits: {max_hit_streak}")
    
    if miss_streaks:
        avg_miss_streak = np.mean(miss_streaks)
        max_miss_streak = max(miss_streaks)
        print(f"Average consecutive miss length: {avg_miss_streak:.2f}")
        print(f"Maximum consecutive misses: {max_miss_streak}")

def analyze_cross_experiment_periodicity(df_details):
    """Cross-experiment periodicity comparison analysis"""
    
    print("\n" + "="*50)
    print("Cross-Experiment Periodicity Comparison")
    print("="*50)
    
    # This function is only called when we have multiple experiments
    experiments = df_details['experiment_name'].unique()
    
    fig, axes = plt.subplots(2, 3, figsize=(18, 10))
    fig.suptitle('Cross-Experiment Hit Periodicity Comparison', fontsize=16, fontweight='bold')
    
    matrix_types = ['A', 'B', 'C']
    
    for idx, matrix_type in enumerate(matrix_types):
        # Subplot 1: Hit rate comparison across experiments
        ax1 = axes[0, idx]
        
        for exp in experiments:
            exp_data = df_details[
                (df_details['experiment_name'] == exp) & 
                (df_details['matrix_type'] == matrix_type)
            ]
            
            if len(exp_data) > 100:
                hit_sequence = exp_data['hit_numeric'].values
                sequence_ids = exp_data['sequence_id'].values
                
                # Calculate moving window hit rate
                window_size = min(50, len(hit_sequence) // 20)
                hit_rate_moving = pd.Series(hit_sequence).rolling(
                    window=window_size, min_periods=1).mean()
                
                ax1.plot(sequence_ids[:500], hit_rate_moving.values[:500], 
                        label=exp, alpha=0.7, linewidth=1.5)
        
        ax1.set_xlabel('Access Sequence Number')
        ax1.set_ylabel('Moving Window Hit Rate')
        ax1.set_title(f'Matrix {matrix_type} - Experiment Comparison')
        ax1.legend(bbox_to_anchor=(1.05, 1), loc='upper left', fontsize=8)
        ax1.grid(True, alpha=0.3)
        ax1.set_ylim(0, 1)
    
    plt.tight_layout()
    plt.savefig('cross_experiment_periodicity.png', dpi=300, bbox_inches='tight')
    plt.show()

def generate_periodicity_report(df_details):
    """Generate periodicity analysis report"""
    
    print("\n=== Periodicity Analysis Report ===")
    
    matrix_types = ['A', 'B', 'C']
    experiments = df_details['experiment_name'].unique()
    
    for matrix_type in matrix_types:
        print(f"\n--- Matrix {matrix_type} Periodicity Characteristics ---")
        
        period_strengths = []
        dominant_periods = []
        
        for exp in experiments:
            exp_data = df_details[
                (df_details['experiment_name'] == exp) & 
                (df_details['matrix_type'] == matrix_type)
            ]
            
            if len(exp_data) > 100:
                hit_sequence = exp_data['hit_numeric'].values
                
                # Periodicity analysis
                try:
                    autocorr = signal.correlate(hit_sequence - np.mean(hit_sequence), 
                                               hit_sequence - np.mean(hit_sequence), 
                                               mode='full')
                    autocorr = autocorr[len(autocorr)//2:]
                    autocorr = autocorr / autocorr[0]
                    
                    # Find periodicity
                    peaks, properties = signal.find_peaks(autocorr[1:min(51, len(autocorr))], 
                                                         height=0.3, distance=5)
                    
                    if len(peaks) > 0:
                        period_strength = np.max(properties['peak_heights'])
                        dominant_period = peaks[np.argmax(properties['peak_heights'])] + 1
                    else:
                        period_strength = 0
                        dominant_period = 0
                    
                    period_strengths.append(period_strength)
                    dominant_periods.append(dominant_period)
                    
                except Exception as e:
                    period_strengths.append(0)
                    dominant_periods.append(0)
        
        if period_strengths:
            # Filter out zero values
            non_zero_strengths = [s for s in period_strengths if s > 0]
            non_zero_periods = [p for p in dominant_periods if p > 0]
            
            if non_zero_strengths:
                avg_strength = np.mean(non_zero_strengths)
                print(f"Average periodicity strength: {avg_strength:.3f}")
            else:
                avg_strength = 0
                print("Average periodicity strength: No significant periodicity detected")
            
            if non_zero_periods:
                avg_period = np.mean(non_zero_periods)
                print(f"Average dominant period: {avg_period:.1f} accesses")
            else:
                print("No clear dominant period detected")
            
            # Classify periodicity type
            if avg_strength > 0.5:
                print("Characteristic: Strong periodic pattern")
            elif avg_strength > 0.3:
                print("Characteristic: Medium periodic pattern")
            elif avg_strength > 0.1:
                print("Characteristic: Weak periodic pattern")
            else:
                print("Characteristic: Random or non-periodic pattern")

if __name__ == "__main__":
    analyze_hit_periodicity()